package top.lingkang.finalgateway.core.tcp;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.lingkang.finalgateway.config.TcpConfig;
import top.lingkang.finalgateway.monitor.IpListCheck;
import top.lingkang.finalgateway.monitor.MonitorInduction;
import top.lingkang.finalgateway.utils.CommonUtils;
import top.lingkang.finalgateway.utils.HttpUtils;

/**
 * @author lingkang
 * @create by 2024/5/23 10:51
 */
@Slf4j
public class TcpService {
    private static final Logger access = LoggerFactory.getLogger("access");
    public final TcpConfig config;
    private EventLoopGroup bossGroup, workerGroup;
    private ChannelFuture future;
    public String error;

    public TcpService(TcpConfig config) {
        this.config = config;
    }

    public void start() {
        if (CommonUtils.portIsUse(config.getPort())) {
            log.error("TCP服务启动失败，端口被占用：{}", config.getPort());
            error = "TCP服务启动失败，端口被占用：" + config.getPort();
            return;
        }

        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup(config.getThreadNumber());
        try {
            // 初始化数据
            initData();

            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 64)
                    // 客户端关闭连接时，自动关闭channel
                    .childOption(ChannelOption.ALLOW_HALF_CLOSURE, true)
                    //置连接为保持活动的状态
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    // 禁用Nagle算法，即使用小数据包即时传输
                    .childOption(ChannelOption.TCP_NODELAY, true);

            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    String ip = HttpUtils.getIpv4Address(ch);
                    if (!config.getIpListCheck().checkIp(ip)) {
                        ch.close();
                        config.getMonitor().accessTcp(ip, config.getPort(), config.getTargetHost(), config.getTargetPort(), 0);
                        access.warn("forbidden tcp ip={}", ip);
                        return;
                    }
                    ch.pipeline().addLast(new TcpProxyHandler(config, ip));
                }
            });

            future = bootstrap.bind(config.getPort());
        } catch (Exception e) {
            log.error("启动tcp服务失败, 端口: {}", config.getPort(), e);
            error = "启动tcp服务失败, 端口: " + config.getPort() + ", " + e.getMessage();
            return;
        }
        log.info("启动 --> tcp: {} --> {}:{}", config.getPort(), config.getTargetHost(), config.getTargetPort());
    }

    private void initData() {
        config.setMonitor(new MonitorInduction(config.getPort()));
        config.setIpListCheck(new IpListCheck(config.getPort()));
    }

    public void shutdown() {
        if (bossGroup == null)
            return;
        bossGroup.shutdownGracefully();
        if (workerGroup != null)
            workerGroup.shutdownGracefully();
        if (future != null) {
            future.channel().close();
        }
        log.info("TCP服务停止. 端口：{}", config.getPort());
    }
}
